/* 
 * Advanced Foundation Classes
 * Copyright (C) 2000/2004  Fabio Rotondo - Andrea Galimberti
 *  
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "pyconnector.h"

//#define AFC_PYCONNECTOR_DEBUG

PyConnector * __internal_pyconnector;

static const char class_name[] = "PyConnector";

// internal functions
static int afc_pyconnector_internal_check_arg_type ( PyConnector *, char *, char, int );
static PyObject * afc_pyconnector_internal_convert_result ( PyConnector *, DynamicClass * );
//static int afc_pyconnector_internal_build_class_script ( PyConnector *, char ** );
static int afc_pyconnector_internal_build_ctypes ( PyConnector * );
static int afc_pyconnector_internal_build_one_ctype ( PyConnector *, DynamicClass *, char * );

// PyConnectorPlugin
static PyConnectorPlugin * afc_pyconnector_internal_plugin_new ( PyConnector *, char * );
static void afc_pyconnector_internal_plugin_dealloc ( PyConnectorPlugin * );
static PyObject * afc_pyconnector_internal_plugin_getattr ( PyConnectorPlugin *, char * );
static int afc_pyconnector_internal_plugin_setattr ( PyConnectorPlugin *, char *, PyObject * );
static PyObject * afc_pyconnector_internal_plugin_execute ( PyConnectorPlugin *, PyObject * );

// PyConnectorObject
static PyObject * afc_pyconnector_internal_object_new ( PyConnector * );
static void afc_pyconnector_internal_object_dealloc ( PyConnectorObject * );

// module
static PyObject * afc_pyconnector_module_execute ( PyObject *, PyObject * );
static PyObject * afc_pyconnector_module_new_plugin ( PyObject *, PyObject * );
static PyObject * afc_pyconnector_module_load_plugin ( PyObject *, PyObject * );
void afc_pyconnector_module_finalize ( void );
void initafcpyconnector ( void );


/*
@node afc_pyconnector_new

	         NAME: afc_pyconnector_new - Initializes a new PyConnector instance

	     SYNOPSIS: PyConnector * afc_pyconnector_new ()

	  DESCRIPTION: This function initializes a new PyConnector instance.

	        INPUT: NONE.

	      RESULTS: a valid inizialized PyConnector structure. NULL in case of errors.

	     SEE ALSO: - afc_pyconnector_delete()
	               - afc_pyconnector_clear()

@endnode
*/

PyConnector * afc_pyconnector_new ()
{
	PyConnector * pycon = ( PyConnector * ) afc_malloc ( sizeof ( PyConnector ) );
	if ( pycon == NULL ) return ( NULL );
	pycon->magic = AFC_PYCONNECTOR_MAGIC;

	if ( ( pycon->plugins = afc_dictionary_new () ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyConnector plugins" );
		afc_pyconnector_delete ( pycon );
		return ( NULL );
	}

	if ( ( pycon->buffer = afc_string_new ( AFC_PYCONNECTOR_BUFFER_SIZE ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyConnector buffer" );
		afc_pyconnector_delete ( pycon );
		return ( NULL );
	}

	if ( ( pycon->pytypes = afc_dictionary_new () ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyConnector pytypes" );
		afc_pyconnector_delete ( pycon );
		return ( NULL );
	}

	if ( ( pycon->pymethods = afc_dictionary_new () ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyConnector pymethods" );
		afc_pyconnector_delete ( pycon );
		return ( NULL );
	}

	if ( ( pycon->tempobjects = afc_array_master_new () ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyConnector tempobjects" );
		afc_pyconnector_delete ( pycon );
		return ( NULL );
	}

	__internal_pyconnector = pycon;

	return ( pycon );
}

/*
@node afc_pyconnector_delete

	         NAME: afc_pyconnector_delete - Disposes a valid PyConnector instance.

	     SYNOPSIS: int afc_pyconnector_delete ( PyConnector * pycon )

	  DESCRIPTION: This function frees an already allocated PyConnector structure.

	        INPUT: - pycon - pointer to a valid PyConnector instance

	      RESULTS: - AFC_ERR_NULL_POINTER - if the pycon pointer is null
	               - AFC_ERR_INVALID_POINTER - if the pycon pointer doesn't
	                 point to a valid PyConnector instance
	               - AFC_ERR_NO_ERROR - if no error occurred

	        NOTES: - this method calls: afc_pyconnector_clear()

	     SEE ALSO: - afc_pyconnector_new()
	               - afc_pyconnector_clear()

*/
int afc_pyconnector_delete ( PyConnector * pycon )
{
	int afc_res; 
	if ( ( afc_res = afc_pyconnector_clear ( pycon ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );

	if ( pycon->plugins ) afc_dictionary_delete ( pycon->plugins );
	if ( pycon->buffer ) afc_string_delete ( pycon->buffer );
	if ( pycon->pytypes ) afc_dictionary_delete ( pycon->pytypes );
	if ( pycon->pymethods ) afc_dictionary_delete ( pycon->pymethods );
	if ( pycon->tempobjects ) afc_array_master_delete ( pycon->tempobjects );

	afc_free ( pycon );

	__internal_pyconnector = NULL;

	return ( AFC_ERR_NO_ERROR );
}

/*
@node afc_pyconnector_clear

	         NAME: afc_pyconnector_clear - Clears the PyConnector instance

	     SYNOPSIS: int afc_pyconnector_clear ( PyConnector * pycon )

	  DESCRIPTION: Use this function to clear all stored data in the current PyConnector instance.

	        INPUT: - pycon - pointer to a valid PyConnector instance

	      RESULTS: - AFC_ERR_NULL_POINTER - if the pycon pointer is null
	               - AFC_ERR_INVALID_POINTER - if the pycon pointer doesn't
	                 point to a valid PyConnector instance
	               - AFC_ERR_NO_ERROR - if no error occurred

	     SEE ALSO: afc_pyconnector_delete()
	               
@endnode

*/
int afc_pyconnector_clear ( PyConnector * pycon )
{
	DynamicClass * dyn = NULL;
	PyTypeObject * ptype = NULL;
	PyMethodDef * pmeth = NULL;

	if ( pycon == NULL ) return ( AFC_ERR_NULL_POINTER );
	if ( pycon->magic != AFC_PYCONNECTOR_MAGIC ) return ( AFC_ERR_INVALID_POINTER );

	if ( pycon->plugins ) {
		dyn = ( DynamicClass * ) afc_dictionary_first ( pycon->plugins );
		while ( dyn != NULL ) {
			afc_dynamic_class_master_delete_instance ( pycon->dynmast, dyn );
			dyn = ( DynamicClass * ) afc_dictionary_next ( pycon->plugins );
		}

		afc_dictionary_clear ( pycon->plugins );
	}

	if ( pycon->pytypes ) {
		ptype = afc_dictionary_first ( pycon->pytypes );
		while ( ptype != NULL ) {
			afc_free ( ptype );
			ptype = afc_dictionary_next ( pycon->pytypes );
		}

		afc_dictionary_clear ( pycon->pytypes );
	}

	if ( pycon->pymethods ) {
		pmeth = afc_dictionary_first ( pycon->pymethods );
		while ( pmeth != NULL ) {
			afc_free ( pmeth );
			pmeth = afc_dictionary_next ( pycon->pymethods );
		}

		afc_dictionary_clear ( pycon->pymethods );
	}

	if ( pycon->buffer ) afc_string_clear ( pycon->buffer );

	if ( pycon->tempobjects ) {
		afc_pyconnector_objects_delete_recursive ( pycon->tempobjects, false );
	}


	pycon->pymodule = NULL;

	return ( AFC_ERR_NO_ERROR );
}

/*
@node afc_pyconnector_init

       NAME: afc_pyconnector_init

   SYNOPSIS: int afc_pyconnector_init ( PyConnector * pycon, DynamicClassMaster * dcm, char * program_name )

DESCRIPTION: initializes the afcpyconnector module and the Python parser. For each dynamic class linked to
             the dynamic class master /dcm/ this function builds the corresponding Python class. Then it
             starts the Python interpreter and loads the /afcpyconnector/ module in the interpreter.

     INPUTS: - pycon - pointer to a valid PyConnector object
             - dcm   - pointer to a DynamicClassMaster object to which some DynamicClasses have been
                       previously linked
             - program_name - if not NULL, this parameter represents the name of the executable that
                              embeds the Python interpreter

    RESULTS: - AFC_ERR_NO_ERROR - if all is ok.
             - see afc_pyconnector_add_commands for other errors that may be raised

   SEE ALSO: - afc_pyconnector_add_commands()

@endnode
*/
int afc_pyconnector_init ( PyConnector * pycon, DynamicClassMaster * dcm, char * program_name )
{
	int afc_res = AFC_ERR_NO_ERROR;

	/* add plugins taking them from the dynamic class master */
	if ( ( afc_res = afc_pyconnector_add_commands ( pycon, dcm ) ) != AFC_ERR_NO_ERROR )
		return ( afc_res );

	/* build the Python objects for the plugins */
	if ( ( afc_res = afc_pyconnector_internal_build_ctypes ( pycon ) ) != AFC_ERR_NO_ERROR )
		return ( afc_res );

	/* Python interpreter initialization */
	if ( program_name != NULL ) Py_SetProgramName ( program_name );
	Py_Initialize ();

	/* internal Python module initialization */
	initafcpyconnector ();

	return ( AFC_ERR_NO_ERROR );
}

/*
       NAME: afc_pyconnector_add_commands

   SYNOPSIS: int afc_pyconnector_add_commands ( PyConnector * pycon, DynamicClassMaster * dynmast )

DESCRIPTION: this function should not be called directly: it is used by the afc_pyconnector_init
             procedure to add to the /pycon/ object the dynamic classes linked to the DynamicClassMaster
             object /dynmast/.

     INPUTS: - pycon   - pointer to a valid PyConnector instance
             - dynmast - pointer to a DynamicClassMaster object

    RESULTS: - AFC_ERR_NO_MEMORY - if there's no memory to create an instance of a command
             - AFC_ERR_NO_ERROR - if all is ok

   SEE ALSO: - afc_pyconnector_init()
*/
int afc_pyconnector_add_commands ( PyConnector * pycon, DynamicClassMaster * dynmast )
{
	DynamicClass * dyn = NULL;
	int afc_res = AFC_ERR_NO_ERROR;
	void * foo = NULL;
	char * class_name = NULL;

	if ( ( pycon == NULL ) || ( dynmast == NULL ) ) return AFC_LOG_FAST ( AFC_ERR_NULL_POINTER );

	/* store the pointer to the dynamic class master */
	pycon->dynmast = dynmast;

	foo = afc_dictionary_first ( dynmast->classes );
	while ( ( foo != NULL ) && ( afc_res == AFC_ERR_NO_ERROR ) ) {
		class_name = ( char * ) afc_dictionary_get_key ( dynmast->classes );

		if ( ( dyn = afc_dynamic_class_master_new_instance ( dynmast, class_name ) ) != NULL ) {
			afc_dictionary_set ( pycon->plugins, class_name, dyn );

		}
		else {
			AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "new plugin instance" );
			break;
		}

		foo = afc_dictionary_next ( dynmast->classes );
	} /* while */

	return ( afc_res );
}

/*
@node afc_pyconnector_pyobject_to_value

       NAME: afc_pyconnector_pyobject_to_value

   SYNOPSIS: void * afc_pyconnector_pyobject_to_value ( PyConnector * pycon, PyObject * value )

DESCRIPTION: converts a Python object to the corresponding C object. The object to be converted is
             contained in the /value/ parameter. Up to now the following conversions are supported:

             Python --> C
             ============

             - Int           --> AFC string
             - String        --> AFC string
             - Dictionary    --> AFC Dictionary
             - List or Tuple --> AFC ArrayMaster

             NOTE: ints are always converted to strings.

             The conversion is recursive.
             All the objects created, except from integers, are not freed: they must be
             freed by the calling procedure.

     INPUTS: - pycon - pointer to a valid PyConnector instance;
             - value - pointer to the PyObject to be converted

    RESULTS: - the converted value

   SEE ALSO: - afc_pyconnector_value_to_pyobject()
             - afc_pyconnector_objects_delete_recursive()

@endnode
*/
void * afc_pyconnector_pyobject_to_value ( PyConnector * pycon, PyObject * value )
{
	void * res = NULL;
	PyObject * key = NULL;
	PyObject * val = NULL;
	int pos = 0;
	int size = 0;
	int k = 0;
	long num = 0;

	if ( PyInt_Check ( value ) ) {
		//ints are converted to strings
		num = PyInt_AsLong ( value );
		sprintf ( pycon->buffer, "%ld", num );
		res = ( void * ) afc_string_dup ( pycon->buffer );
	}

	if ( PyString_Check ( value ) ) {
		res = ( void * ) afc_string_dup ( PyString_AsString ( value ) );
	}

	if ( PyDict_Check ( value ) ) {
		res = afc_dictionary_new ();

		if ( res != NULL ) {
			while ( PyDict_Next ( value, &pos, &key, &val ) ) {
				afc_dictionary_set ( res, PyString_AsString ( key ), afc_pyconnector_pyobject_to_value ( pycon, val ) );
			}
		}
	}

	if ( PyList_Check ( value ) ) {
		res = afc_array_master_new ();

		size = PyList_Size ( value );

		for ( k = 0; k < size; k++ ) {
			afc_array_master_add ( res, afc_pyconnector_pyobject_to_value ( pycon, PyList_GetItem ( value, k ) ), AFC_ARRAY_MASTER_ADD_TAIL );
		}
	}

	if ( PyTuple_Check ( value ) ) {
		res = afc_array_master_new ();

		size = PyTuple_Size ( value );

		for ( k = 0; k < size; k++ ) {
			afc_array_master_add ( res, afc_pyconnector_pyobject_to_value ( pycon, PyTuple_GetItem ( value, k ) ), AFC_ARRAY_MASTER_ADD_TAIL );
		}
	}

	return ( res );
}

/*
@node afc_pyconnector_tuple_to_array_master

       NAME: afc_pyconnector_tuple_to_array_master

   SYNOPSIS: int afc_pyconnector_tuple_to_array_master ( PyConnector * pycon, PyObject * tuple, ArrayMaster * dest, int start, char * args_template )

DESCRIPTION: adds the objects contained in the /tuple/ parameter (a Python's tuple) to the ArrayMaster /dest/.
             The objects contained in /tuple/ are converted in the corresponding C datatypes: see the
             afc_pyconnector_pyobject_to_value() function for more info on the conversion rules.

             The /start/ parameter is the number (from zero) of the first item in the tuple to be converted.

             If /args_template/ is not NULL it contains a template representing the types of the /tuple/'s items:
             while converting the items this functions checks their types against the template and, if there's a type
             mismatch, it raises the TypeError exception and returns the AFC_PYCONNECTOR_ERR_TYPE_ERROR returncode.
             The template is a string containing one character per item, each representing the type of the corresponding
             item (if /start/ is not zero, then the first character corresponds to the /start/ item):

             - I --> integer
             - S --> string
             - D --> dictionary
             - L --> list or tuple 

             A final (optional) "*" character in the template means that the preceding type should be repeated for all the
             remaining items.

             NOTE: dictionaries, lists or tuples MUST NOT contain numbers as values: this is because in C there's no way
                   to know if a number is a pointer or an integer (or a float...).

             NOTE: objects created during the conversion and stored in /dest/ must be freed by the calling
                   procedure (maybe by means of the afc_pyconnector_objects_delete_recursive() function).

     INPUTS: - pycon - pointer to a valid PyConnector instance
             - tuple - pointer to a Python's tuple
             - dest  - pointer to an ArrayMaster object
             - start - first item in the tuple to be processed (zero based)
             - args_template - string containing the sequence of the types of the tuple's items to typecheck them
                               while converting (if this parameter is NULL then no typechecking is done)

    RESULTS: - AFC_ERR_NULL_POINTER           - if /pycon/ or /tuple/ or /dest/ are NULL
             - AFC_ERR_INVALID_POINTER        - if /tuple/ is not a valid Python tuple
             - AFC_PYCONNECTOR_ERR_TYPE_ERROR - if there's a type mismatch while doing typechecking with the template

@endnode
*/
int afc_pyconnector_tuple_to_array_master ( PyConnector * pycon, PyObject * tuple, ArrayMaster * dest, int start, char * args_template )
{
	int size = 0;
	int k = 0;
	PyObject * item;
	int afc_res = AFC_ERR_NO_ERROR;
	int arg_num = 0;
	void * res = NULL;


	if ( ( pycon == NULL ) || ( tuple == NULL ) || ( dest == NULL ) ) return AFC_LOG_FAST ( AFC_ERR_NULL_POINTER );

	if ( ! PyTuple_Check ( tuple ) ) return AFC_LOG_FAST ( AFC_ERR_INVALID_POINTER );

	size = PyTuple_Size ( tuple );

	for ( k = start; k < size; k++ ) {
		item = PyTuple_GetItem ( tuple, k );
		arg_num = k - start + 1;

		// integer
		if ( PyInt_Check ( item ) ) {
			if ( ( afc_res = afc_pyconnector_internal_check_arg_type ( pycon, args_template, 'I', arg_num ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );
		}

		// string
		if ( PyString_Check ( item ) ) {
			if ( ( afc_res = afc_pyconnector_internal_check_arg_type ( pycon, args_template, 'S', arg_num ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );
		}

		if ( PyDict_Check ( item ) ) {
			if ( ( afc_res = afc_pyconnector_internal_check_arg_type ( pycon, args_template, 'D', arg_num ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );
		}

		if ( PyList_Check ( item ) || PyTuple_Check ( item ) ) {
			if ( ( afc_res = afc_pyconnector_internal_check_arg_type ( pycon, args_template, 'L', arg_num ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );
		}

		if ( ( res = afc_pyconnector_pyobject_to_value ( pycon, item ) ) != NULL ) {
			//add the result to the destination array master
			afc_array_master_add ( dest, res, AFC_ARRAY_MASTER_ADD_TAIL );

			//add the result to the list of objects to be deleted
			afc_array_master_add ( pycon->tempobjects, res, AFC_ARRAY_MASTER_ADD_TAIL );
		}

		if ( args_template != NULL ) {
			args_template++;
			if ( *args_template == '*' ) args_template--;
		}
	}

	return ( AFC_ERR_NO_ERROR );
}

/*
@node afc_pyconnector_execute

       NAME: afc_pyconnector_execute

   SYNOPSIS: int afc_pyconnector_execute ( PyConnector * pycon, DynamicClass * dyn, char * method_name, PyObject * params, int start )

DESCRIPTION: executes the method with name /method_name/ contained in the DynamicClass /dyn/. The method is called with
             actual parameters taken from the /params/ tuple: the first parameter to be passed is specified by the /start/
             integer (the first item in the tuple corresponds to start = 0).
             The /params/ object must be a valid Python's tuple: its items (starting from the one specified by the /start/
             argument) are converted to their corresponding C types as described in the afc_pyconnector_tuple_to_array_master()
             function before calling the method. If the DynamicClass /dyn/ contains the specification of the types of the parameters
             accepted by the /method_name/ method (see the DynamicClass reference for more info on this subject), then this
             function does typechecking on the actual values received from Python: if there's a type mismatch then the TypeError
             exception is raised and the function returns the AFC_PYCONNECTOR_ERR_TYPE_ERROR code.

             If the /method_name/ doesn't exist in the DynamicClass /dyn/ this function raises the NameError exception and
             returns the AFC_PYCONNECTOR_ERR_METHOD_UNKNOWN error code.

             NOTE: the parameters passed to the DynamicClass' method /method_name/ (using the dyn->args field)
                   MUST NOT be changed in the method itself: they should be considered as READ ONLY.
             NOTE: dictionaries, lists or tuples contained in the /params/ tuple MUST NOT contain numbers as values:
                   this is because in C there's no way to know if a number is a pointer or an integer (or a float...).

             Usually this function is not called directly, but it is used from other higher-level functions.

     INPUTS: - pycon - pointer to a valid PyConnector instance
             - dyn   - pointer to a DynamicClass object
             - method_name - name of the method to be executed
             - params - Pyhton's tuple containing the actual parameters to be passed to the method
             - start - first parameter to be used (from 0)

    RESULTS: - AFC_ERR_NULL_POINTER - if /dyn/ is NULL
             - AFC_PYCONNECTOR_ERR_METHOD_UNKNOWN - if the /method_name/ method is not defined in the dynamic class /dyn/
             - AFC_PYCONNECTOR_ERR_TYPE_ERROR - if the typechecking on the actual parameters contained in the /params/ tuple fails.

   SEE ALSO: - afc_pyconnector_tuple_to_array_master()

@endnode
*/
int afc_pyconnector_execute ( PyConnector * pycon, DynamicClass * dyn, char * method_name, PyObject * params, int start )
{
	DynamicClassMethodData * dcmd = NULL;
	ArrayMaster * args;
	int afc_res = AFC_ERR_NO_ERROR;

	if ( pycon == NULL ) return AFC_LOG_FAST ( AFC_ERR_NULL_POINTER );

	if ( dyn != NULL ) 
	{
		// Get the DynamicClassMethodData info about the method being called
		if ( ( dcmd = afc_dynamic_class_find_method ( dyn, method_name ) ) == NULL ) {

			PyErr_SetString ( PyExc_NameError, afc_string_make ( pycon->buffer, "Method \"%s\" unknown", method_name ) );
			return AFC_LOG ( AFC_LOG_ERROR, AFC_PYCONNECTOR_ERR_METHOD_UNKNOWN, "Method unknown", method_name );
		}

		// Populate the Dynamic Class array. The array already exists inside the DC,
		// so we simply use it (by taking a pointer from the dc) after cleaning its contents
		args = dyn->args;
		afc_array_master_clear ( args );

		if ( ( afc_res = afc_pyconnector_tuple_to_array_master ( pycon, params, args, start, dcmd->params ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );

		// Call afc_dynamic_class_execute_vars() with last param (vars) set to NULL
		// to avoid argument parsing by DC.

		afc_res = afc_dynamic_class_execute_vars ( dyn, method_name, NULL );

		// delete temporary objects created by afc_pyconnector_tuple_to_array_master()
		afc_pyconnector_objects_delete_recursive ( pycon->tempobjects, false );

		return ( afc_res );
	}
	else {
		PyErr_SetString ( PyExc_NameError, "Dynamic Class NULL pointer" );
		return AFC_LOG_FAST_INFO ( AFC_ERR_NULL_POINTER, "Dynamic Class' pointer" );
	}
}

/*
@node afc_pyconnector_value_to_pyobject

       NAME: afc_pyconnector_value_to_pyobject

   SYNOPSIS: PyObject * afc_pyconnector_value_to_pyobject ( PyConnector * pycon, int kind, void * value )

DESCRIPTION: converts a C value to a Python object of the right type. The types accepted are:

             - AFC_DYNAMIC_CLASS_VAR_KIND_NUM    - performs the conversion from int to Python's integer;
             - AFC_DYNAMIC_CLASS_VAR_KIND_STRING - performs the conversion from (char *) to Python's string;
             - AFC_DYNAMIC_CLASS_VAR_KIND_POINTER  - performs the conversion from an AFC class to the
                                                     corresponding Python object.

             In case of AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, the type of object to be processed
             should be an AFC class: its type is deduced from the value of the "magic" property.
             Up to now the following classes are supported:

             - AFC Dictionary: it is converted to a Python dictionary object; the items in the
                               AFC Dictionary object are processed recursively calling this same
                               function with the "kind" parameter set to AFC_DYNAMIC_CLASS_VAR_KIND_POINTER.

             - AFC ArrayMaster, NodeMaster, StringNode : it is converted to a Python list object; the items
                               in the AFC object are processed recursively calling this same function
                               with the "kind" parameter set to AFC_DYNAMIC_CLASS_VAR_KIND_POINTER.

             - if none of the above: the object contained in "value" is supposed to be a string (char *);
                                     this means that you MUST NOT use a number as a value of an AFC Dictionary,
                                     ArrayMaster, NodeMaster or StringNode: this is because in C there's no way to
                                     know if a number is a pointer or an integer.

     INPUTS: - pycon - pointer to a valid PyConnector object
             - kind  - type of value to be converted (one of AFC_DYNAMIC_CLASS_VAR_KIND_* values)
             - value - value to be converted

    RESULTS: the PyObject resulting from the conversion, or Py_None if something failed.

@endnode
*/
PyObject * afc_pyconnector_value_to_pyobject ( PyConnector * pycon, int kind, void * value )
{
	PyObject * res = NULL;
	PyObject * pyobj = NULL;
	char * item = NULL;

	if ( value != NULL ) {
		switch ( kind ) {
			case AFC_DYNAMIC_CLASS_VAR_KIND_NUM:
				res = Py_BuildValue ( "i", value );
				break;

			case AFC_DYNAMIC_CLASS_VAR_KIND_STRING:
				res = Py_BuildValue ( "s", value );
				break;

			case AFC_DYNAMIC_CLASS_VAR_KIND_POINTER:

				switch ( AFC_CLASS_MAGIC( value ) ) {
					case AFC_DICTIONARY_MAGIC:
						res = PyDict_New ();

						if ( res != NULL ) {
							item = ( char * ) afc_dictionary_first ( ( Dictionary * ) value );

							while ( item ) {
								pyobj = afc_pyconnector_value_to_pyobject ( pycon, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, item );
								PyDict_SetItemString ( res, afc_dictionary_get_key ( ( ( Dictionary * ) value ) ), pyobj );
								Py_DECREF ( pyobj );

								item = ( char * ) afc_dictionary_next ( ( Dictionary * ) value );
							}
						}
						break;

					case AFC_ARRAY_MASTER_MAGIC:
						res = PyList_New ( afc_array_master_len ( value ) );

						if ( res != NULL ) {
							item = ( char * ) afc_array_master_first ( value );

							while ( item ) {
								PyList_SetItem ( res, afc_array_master_pos ( ( ( ArrayMaster * ) value ) ), afc_pyconnector_value_to_pyobject ( pycon, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, item ) );

								item = ( char * ) afc_array_master_next ( value );
							}
						}
						break;

					case AFC_NODEMASTER_MAGIC:
						res = PyList_New ( afc_nodemaster_len ( ( ( NodeMaster * ) value ) ) );

						if ( res != NULL ) {
							item = ( char * ) afc_nodemaster_first ( value );

							while ( item ) {
								PyList_SetItem ( res, afc_nodemaster_pos ( ( ( NodeMaster * ) value ) ), afc_pyconnector_value_to_pyobject ( pycon, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, item ) );

								item = ( char * ) afc_nodemaster_next ( value );
							}
						}
						break;

					case AFC_STRINGNODE_MAGIC:
						res = PyList_New ( afc_stringnode_len ( ( ( StringNode * ) value ) ) );

						if ( res != NULL ) {
							item = ( char * ) afc_stringnode_first ( ( ( StringNode * ) value ) );

							while ( item ) {
								PyList_SetItem ( res, afc_stringnode_pos ( ( ( StringNode * ) value ) ), afc_pyconnector_value_to_pyobject ( pycon, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, item ) );

								item = ( char * ) afc_stringnode_next ( ( ( StringNode * ) value ) );
							}
						}
						break;

						default:
							res = afc_pyconnector_value_to_pyobject ( pycon, AFC_DYNAMIC_CLASS_VAR_KIND_STRING, value );
				} // switch

				break;
		} // switch
	}

	// if nothing has been set we return None
	if ( res == NULL ) {
		Py_INCREF ( Py_None );
		res = Py_None;
	}

	return ( res );
}

/*
@node afc_pyconnector_objects_delete_recursive

       NAME: afc_pyconnector_objects_delete_recursive

   SYNOPSIS: int afc_pyconnector_objects_delete_recursive ( void * value, int delouter )

DESCRIPTION: clears the object contained in /value/ and then deletes it. The deletion
             process is recursive: all the objects contained in the object in /value/ are
             deleted before the object in /value/ is deleted.
             If /delouter/ is true then the object in /value/ is deleted, otherwise it
             is only cleared: this implies that the objects contained in it are nevertheless
             always deleted; the /delouter/ flag affects only the "outer" object in the
             hierarchy (that is the one which /value/ points to).

     INPUTS: - value    - object to be deleted
             - delouter - if it is false, the object in /value/ is only cleared, but not deleted

    RESULTS: - AFC_ERR_NO_ERROR

@endnode
*/
int afc_pyconnector_objects_delete_recursive ( void * value, int delouter )
{
	void * item = NULL;

	switch ( AFC_CLASS_MAGIC( value ) ) {
		case AFC_ARRAY_MASTER_MAGIC:
			item = afc_array_master_first ( value );

			while ( item != NULL ) {
				afc_pyconnector_objects_delete_recursive ( item, true );
				item = afc_array_master_next ( value );
			}

			if ( delouter ) {
				afc_array_master_delete ( value );
			}
			else
				afc_array_master_clear ( value );

			break;

		case AFC_NODEMASTER_MAGIC:
			item = afc_nodemaster_first ( value );

			while ( item != NULL ) {
				afc_pyconnector_objects_delete_recursive ( item, true );
				item = afc_nodemaster_next ( value );
			}

			if ( delouter ) {
				afc_nodemaster_delete ( value );
			}
			else
				afc_nodemaster_clear ( value );

			break;

		case AFC_STRINGNODE_MAGIC:
			if ( delouter ) {
				afc_stringnode_delete ( value );
			}
			else
				afc_stringnode_clear ( value );

			break;

		case AFC_DICTIONARY_MAGIC:
			item = afc_dictionary_first ( value );

			while ( item != NULL ) {
				afc_pyconnector_objects_delete_recursive ( item, true );
				item = afc_dictionary_next ( value );
			}

			if ( delouter ) {
				afc_dictionary_delete ( value );
			}
			else
				afc_dictionary_clear ( value );

			break;

		default:
			afc_string_delete ( value );

	} //switch

	return ( AFC_ERR_NO_ERROR );
}


/************************
    Internal functions
 ************************/

/*
       NAME: afc_pyconnector_internal_check_arg_type

   SYNOPSIS: int afc_pyconnector_internal_check_arg_type ( PyConnector * pycon, char * args_template, char curr_type, int arg_num )

DESCRIPTION: checks if the type /curr_type/ is equal to the type specified in /args_template/; if this is not the case, the
             function raises a TypeError exception and returns the AFC_PYCONNECTOR_ERR_TYPE_ERROR code.
             The value of /curr_type/ is a character representing the type to be checked:
               - S stands for string
               - I stands for integer
             The /args_template/ parameter is a pointer to a character representing the correct type: allowed values are the
             same as for /curr_type/ (see above).

             The /arg_num/ parameter is used only to compose the exception's message that is shown to the user by the Python
             interpreter.

     INPUTS: - pycon - pointer to a valid PyConnector instance
             - args_template - pointer to a character representing the correct type
             - curr_type - character representing the type to be checked against the /args_template/ parameter
             - arg_num - number used to show to the user which of the arguments raised a TypeError exception (e.g., this
                         is useful when there is more than one argument, maybe contained in a tuple).

    RESULTS: - AFC_PYCONNECTOR_ERR_TYPE_ERROR - if the types specified in /args_template/ and /curr_type/ don't match
             - AFC_ERR_NO_ERROR - otherwise

   SEE ALSO: - afc_pyconnector_tuple_to_array_master()

*/
static int afc_pyconnector_internal_check_arg_type ( PyConnector * pycon, char * args_template, char curr_type, int arg_num )
{
	if ( args_template != NULL ) {
		if ( *args_template != curr_type ) {
			PyErr_SetString ( PyExc_TypeError, afc_string_make ( pycon->buffer, "Wrong type of arg %d: should be %c", arg_num, *args_template ) );
			return ( AFC_PYCONNECTOR_ERR_TYPE_ERROR );
		}
	}

	return ( AFC_ERR_NO_ERROR );
}

/*
       NAME: afc_pyconnector_internal_convert_result

   SYNOPSIS: PyObject * afc_pyconnector_internal_convert_result ( PyConnector * pycon, DynamicClass * dyn )

DESCRIPTION: gets the /result/ field of the dynamic class /dyn/ and coverts it to the corresponding
             Python object.
             The recognized types of /result/ (contained in the /result_type/ field of the dynamic class) are:
               - AFC_DYNAMIC_CLASS_RESULT_TYPE_INTEGER     - converted to a Python's integer object
               - AFC_DYNAMIC_CLASS_RESULT_TYPE_STRING      - converted to a Python's string object
               - AFC_DYNAMIC_CLASS_RESULT_TYPE_DICTIONARY  - converted to a Python's dictionary object
               - AFC_DYNAMIC_CLASS_RESULT_TYPE_POINTER     - must be an AFC class instance: it is converted to
                                                             a Python object. The correspondence between AFC
                                                             classes and Python objects are described in the
                                                             afc_pyconnector_value_to_pyobject() function

             Note: in case of AFC_DYNAMIC_CLASS_RESULT_TYPE_DICTIONARY the items in the AFC Dictionary
                   object are supposed to be strings (that is of type "char *").

     INPUTS: - pycon - pointer to a valid PyConnector instance
             - dyn   - dynamic class whose /result/ field should be converted

    RESULTS: the PyObject generated from the conversion, or Py_None if something went wrong.

   SEE ALSO: - afc_pyconnector_value_to_pyobject()
*/
static PyObject * afc_pyconnector_internal_convert_result ( PyConnector * pycon, DynamicClass * dyn )
{
	int kind = 0;

	if ( dyn != NULL ) {

		// the types of result must be converted to the equivalent types
		// of variables before calling afc_pyconnector_value_to_pyobject()
		switch ( dyn->result_type ) {
			case AFC_DYNAMIC_CLASS_RESULT_TYPE_INTEGER:
				kind = AFC_DYNAMIC_CLASS_VAR_KIND_NUM;
				break;

			case AFC_DYNAMIC_CLASS_RESULT_TYPE_STRING:
				kind = AFC_DYNAMIC_CLASS_VAR_KIND_STRING;
				break;

			case AFC_DYNAMIC_CLASS_RESULT_TYPE_DICTIONARY:
				kind = AFC_DYNAMIC_CLASS_VAR_KIND_POINTER;
				break;

			case AFC_DYNAMIC_CLASS_RESULT_TYPE_POINTER:
				kind = AFC_DYNAMIC_CLASS_VAR_KIND_POINTER;
				break;
		}

		return afc_pyconnector_value_to_pyobject ( pycon, kind, dyn->result );
	}

	else {
		Py_INCREF ( Py_None );
		return Py_None;
	}
}

/*
       NAME: afc_pyconnector_internal_build_ctypes

   SYNOPSIS: int afc_pyconnector_internal_build_ctypes ( PyConnector * pycon )

DESCRIPTION: starting from the dynamic classes linked to the /pycon/ PyConnector object by means of the
             afc_pyconnector_init() function, this method builds all the required PyTypeObject and PyMethodDef
             structures needed to extend the Python interpreter.

             For each dynamic class the function afc_pyconnector_internal_build_one_ctype() is called.

     INPUTS: - pycon - pointer to a valid (and intialized) PyConnector object

    RESULTS: - AFC_ERR_NO_MEMORY - if a memory allocation fails
             - AFC_ERR_NO_ERROR  - otherwise

   SEE ALSO: - afc_pyconnector_internal_build_one_ctype()
*/
static int afc_pyconnector_internal_build_ctypes ( PyConnector * pycon )
{
	DynamicClass * dyn = NULL;
	int afc_res = AFC_ERR_NO_ERROR;
	char * class_name = NULL;

	dyn = afc_dictionary_first ( pycon->plugins );
	while ( dyn != NULL ) {
		class_name = afc_dictionary_get_key ( pycon->plugins );

		if ( ( afc_res = afc_pyconnector_internal_build_one_ctype ( pycon, dyn, class_name ) ) != AFC_ERR_NO_ERROR )
			return ( afc_res );

		dyn = afc_dictionary_next ( pycon->plugins );
	} //while

	return ( AFC_ERR_NO_ERROR );
}

/*
       NAME: afc_pyconnector_internal_build_one_ctype

   SYNOPSIS: int afc_pyconnector_internal_build_one_ctype ( PyConnector * pycon, DynamicClass * dyn, char * class_name )

DESCRIPTION: builds the PyTypeObject and PyMethodDef structures corresponding to the dynamic
             class /dyn/. The name of this class is contained in the /class_name/ parameter.

     INPUTS: - pycon - pointer to a valid PyConnector instance
             - dyn - pointer to a DynamicClass object
             - class_name - name of the dynamic class pointed to by /dyn/

    RESULTS: - AFC_ERR_NO_MEMORY - if a memory allocation fails
             - AFC_ERR_NO_ERROR  - otherwise

   SEE ALSO: - afc_pyconnector_internal_build_ctypes()
*/
static int afc_pyconnector_internal_build_one_ctype ( PyConnector * pycon, DynamicClass * dyn, char * class_name )
{
	PyTypeObject * newtype = NULL;
	PyMethodDef * typemethods = NULL;
	int num_methods = 0;
	DynamicClassMethodData * dcmd = NULL;
	int k = 0;

	// allocate a new PyTypeObject structure

	if ( ( newtype = ( PyTypeObject * ) afc_malloc ( sizeof ( PyTypeObject ) ) ) == NULL )
		return AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "Cannot allocate PyTypeObject" );

	afc_dictionary_set ( pycon->pytypes, class_name, newtype );

	// fill in the slots

	/* header */
	//newtype->_ob_next = 0;
	//newtype->_ob_prev = 0;
	newtype->ob_refcnt = 1;
	newtype->ob_type = &PyType_Type;
	newtype->ob_size = 0;
	newtype->tp_name = class_name;
	newtype->tp_basicsize = sizeof ( PyConnectorPlugin );
	newtype->tp_itemsize = 0;

	/* standard methods */
	newtype->tp_dealloc = ( destructor ) afc_pyconnector_internal_plugin_dealloc;
	newtype->tp_getattr = ( getattrfunc ) afc_pyconnector_internal_plugin_getattr;
	newtype->tp_setattr = ( setattrfunc ) afc_pyconnector_internal_plugin_setattr;

	/* number of methods published by the dynamic class */
	num_methods = afc_dictionary_len ( dyn->methods );

	/* build the methods' lookup table */
	if ( num_methods >0 ) {
		if ( ( typemethods = afc_malloc ( sizeof ( PyMethodDef ) * ( num_methods + 1 ) ) ) == NULL )
			return AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "Cannot allocate PyMethodDef" );

		afc_dictionary_set ( pycon->pymethods, class_name, typemethods );

		dcmd = afc_dictionary_first ( dyn->methods );
		k = 0;
		while ( dcmd != NULL ) {
			typemethods[ k ].ml_name = dcmd->name;
			typemethods[ k ].ml_meth = ( PyCFunction ) afc_pyconnector_internal_plugin_execute;
			typemethods[ k ].ml_flags = METH_VARARGS;

			k++;
			dcmd = afc_dictionary_next ( dyn->methods );
		}
		/* sentinel */
		typemethods[ k ].ml_name = NULL;
		typemethods[ k ].ml_meth = NULL;
	}

	return ( AFC_ERR_NO_ERROR );
}


/*********************************
     PyConnectorPlugin methods
 *********************************/

/*
       NAME: afc_pyconnector_internal_plugin_execute

   SYNOPSIS: PyObject * afc_pyconnector_internal_plugin_execute ( PyConnectorPlugin * self, PyObject * args )

DESCRIPTION: this function is called by the Python interpreter whenever an object's method is executed.
             The /self/ parameter contains the pointer to the C structure corresponding to the Python
             object upon which the method is executed; the /args/ parameter contains the tuple of
             arguments passed to the method.
             Note that the name of the method to be executed is contained in the /attribute_requested/ field
             of the C structure: the name of the method is stored in this field by the afc_pyconnector_internal_plugin_getattr()
             function that is called by Python before calling afc_pyconnector_internal_plugin_execute().

     INPUTS: - self - pointer to the C structure corresponding to the Python object whose method must be executed;
             - args - tuple of actual parameters passed to the method being called.

    RESULTS: - NULL - if something went wrong; to see which exceptions are raised look at the documentation of the
                      afc_pyconnector_execute() function;
             - otherwise the value returned by afc_pyconnector_execute() (and thus by the dynamic class' function being
               called) is converted to a Python object and returned.

   SEE ALSO: - afc_pyconnector_execute()

*/
static PyObject * afc_pyconnector_internal_plugin_execute ( PyConnectorPlugin * self, PyObject * args )
{
	PyConnector * pycon = __internal_pyconnector;
	int res = AFC_ERR_NO_ERROR;

	/* Exceptions are raised in the afc_pyconnector_execute function */
	if ( ( res = afc_pyconnector_execute ( self->parent, self->dclass, self->attribute_requested, args, 0 ) ) != AFC_ERR_NO_ERROR ) {

		/* if an error occurred but no exception has been set, we set a generic exception */
		if ( PyErr_Occurred () == NULL )
			PyErr_SetString ( pycon->base_exception, afc_string_make ( pycon->buffer, "Method \"%s\" returned <%d>: %s", self->attribute_requested, res, AFC_STR_ERROR() ) );

		return ( NULL );
	}
	return afc_pyconnector_internal_convert_result ( self->parent, self->dclass );
}

/*
       NAME: afc_pyconnector_internal_plugin_getattr

   SYNOPSIS: PyObject * afc_pyconnector_internal_plugin_getattr ( PyConnectorPlugin * self, char * name )

DESCRIPTION: this function is called by the Python interpreter when an attribute of a Python object
             has been requested, whether it is an object's variable or a method.
             The requested attribute is searched for in the dynamic class corresponding to the
             Python's object used in the script:
             - if the attribute is a variable, then its value is converted to a Python object and returned
               as a result;
             - if the attribute is a method, then it is searched in the method's table to see which C function
               should be executed;
             - if the requested attribute doesn't exist, then Py_None is returned (and the Python's interpreter
               will raise an exception).

             Note that the attribute is first searched in the variables' dictionary, so if a variable has the
             same name of a method the latter is hidden an whould never be found.

     INPUTS: - self - pointer to the C structure corresponding to the Python object for which an attribute has been
                      requested
             - name - name of the attribute being requested.

    RESULTS: a PyObject containing the value of the attribute (be it a variable or a method) or Py_None if
             the attribute doesn't exist.

   SEE ALSO: - afc_pyconnector_internal_plugin_setattr()

*/
static PyObject * afc_pyconnector_internal_plugin_getattr ( PyConnectorPlugin * self, char * name )
{
	PyMethodDef * methodtab = NULL;
	DynamicClassVar * dcvar = NULL;

	afc_string_copy ( self->attribute_requested, name, ALL );

	// if "name" is a variable, then return its Python equivalent
	if ( self->dclass->vars != NULL ) {
		if ( ( dcvar = afc_dictionary_get ( self->dclass->vars, name ) ) != NULL ) {
			return afc_pyconnector_value_to_pyobject ( self->parent, dcvar->kind, dcvar->value );
		}
	}

	// get the method's table of the current CType
	if ( ( methodtab = ( PyMethodDef * ) afc_dictionary_get ( self->parent->pymethods, self->class_name ) ) != NULL ) {

		return Py_FindMethod ( methodtab, ( PyObject * ) self, name );
	}
	else {
		Py_INCREF ( Py_None );
		return Py_None;
	}
}

/*
       NAME: afc_pyconnector_internal_plugin_setattr

   SYNOPSIS: int afc_pyconnector_internal_plugin_setattr ( PyConnectorPlugin * self, char * name, PyObject * value )

DESCRIPTION: this function is called by the Python interpreter when the value of a Python object's attribute
             is being set. The attribute's name is searched in the dynamic class corresponding to the
             Python object being used: if it has been found, then the value received from the Python's interpreter
             is converted to its C equivalent and stored in the dynamic class' variable.
             If the attribute has not been found and exception is raised and the function returns 1.

     INPUTS: - self - pointer to the C structure corresponding to the Python object for which an attribute's value
                      must be set;
             - name - name of the attribute being used;
             - value - object containing the value to be stored in the attribute.

    RESULTS: - 1 - if the attribute doesn't exist
             - 0 - otherwise

   SEE ALSO: - afc_pyconnector_internal_plugin_getattr()

*/
static int afc_pyconnector_internal_plugin_setattr ( PyConnectorPlugin * self, char * name, PyObject * value )
{
	if ( self->dclass->vars != NULL ) {
		if ( afc_dictionary_get ( self->dclass->vars, name ) != NULL ) {

			// Integer
			if ( PyInt_Check ( value ) ) {
				afc_dynamic_class_set_var ( self->dclass, AFC_DYNAMIC_CLASS_VAR_KIND_NUM, name, ( void * ) PyInt_AsLong ( value ) );
			}

			// String
			if ( PyString_Check ( value ) ) {
				afc_dynamic_class_set_var ( self->dclass, AFC_DYNAMIC_CLASS_VAR_KIND_STRING, name, ( void * ) PyString_AsString ( value ) );
			}

		}
		else {
			PyErr_SetString ( PyExc_NameError, afc_string_make ( self->parent->buffer, "Attribute \"%s\" unknown", name ) );
			return ( 1 );
		}
	}
	else {
		PyErr_SetString ( PyExc_NameError, afc_string_make ( self->parent->buffer, "Attribute \"%s\" unknown", name ) );
		return ( 1 );
	}

	return ( 0 );
}

/*
       NAME: afc_pyconnector_internal_plugin_dealloc

   SYNOPSIS: void afc_pyconnector_internal_plugin_dealloc ( PyConnectorPlugin * self )

DESCRIPTION: disposes of the memory previously allocated for a PyConnectorPlugin object.
             Besides, this function instructs the Python interpreter to destroy its corresponding object.

     INPUTS: - self - pointer to a valid PyConnectorPlugin object to be deallocated

    RESULTS: None

   SEE ALSO: - afc_pyconnector_internal_plugin_new()
*/
static void afc_pyconnector_internal_plugin_dealloc ( PyConnectorPlugin * self )
{
	#ifdef AFC_PYCONNECTOR_DEBUG
		fprintf ( stderr, "*** Entering: afc_pyconnector_internal_plugin_dealloc\n" );
	#endif

	if ( self->dclass ) afc_dynamic_class_master_delete_instance ( self->parent->dynmast, self->dclass );

	#ifdef AFC_PYCONNECTOR_DEBUG
		fprintf ( stderr, "*** Deleted: dynamic class instance\n" );
	#endif

	if ( self->class_name ) afc_string_delete ( self->class_name );

	#ifdef AFC_PYCONNECTOR_DEBUG
		fprintf ( stderr, "*** Deleted: class_name string\n" );
	#endif

	if ( self->attribute_requested ) afc_string_delete ( self->attribute_requested );

	#ifdef AFC_PYCONNECTOR_DEBUG
		fprintf ( stderr, "*** Deleted: attribute_requested string\n" );
	#endif

	PyObject_DEL ( self );
}

/*
       NAME: afc_pyconnector_internal_plugin_new

   SYNOPSIS: PyConnectorPlugin * afc_pyconnector_internal_plugin_new ( PyConnector * pycon, char * class_name )

DESCRIPTION: creates a new instance of a PyConnectorPlugin object.
             A PyConnectorPlugin instance is the C structure corresponding to a Python object instance, thus
             it contains all the data pertaining to a specific Python object.

             This function is called by the afc_pyconnector_module_new_plugin() function when the Python's script
             requires the creation of a new object instance.

             If the specified class doesn't exists then this function returns AFC_PYCONNECTOR_ERR_CLASS_UNKNOWN
             and raises the PyConnectorError exception.
             If the PyObject_NEW() method used to create a new Python object fails, then AFC_ERR_NO_MEMORY is
             returned and the PyConnectorError exception is raised.

     INPUTS: - pycon - pointer to a valid PyConnector instance;
             - class_name - name of the class which the new object should be an instance of.

    RESULTS: - AFC_PYCONNECTOR_ERR_CLASS_UNKNOWN - if the class_name class doesn't exist (it's not a valid
               Python type corresponding to a loaded dynamic class)
             - AFC_ERR_NO_MEMORY - if at least one piece of the object cannot be allocated
             - the pointer to the PyConnectorPlugin object if all it's ok.

   SEE ALSO: - afc_pyconnector_internal_plugin_dealloc()
*/
static PyConnectorPlugin * afc_pyconnector_internal_plugin_new ( PyConnector * pycon, char * class_name )
{
	PyTypeObject * ptype = NULL;
	PyConnectorPlugin * self = NULL;

	if ( ( ptype = afc_dictionary_get ( pycon->pytypes, class_name ) ) == NULL ) {
		AFC_LOG ( AFC_LOG_ERROR, AFC_PYCONNECTOR_ERR_CLASS_UNKNOWN, "Class unknown", class_name );
		PyErr_SetString ( pycon->base_exception, "Class name unknown" );
		return ( NULL );
	}

	if ( ( self = PyObject_NEW ( PyConnectorPlugin, ptype ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyObject_NEW" );
		PyErr_SetString ( pycon->base_exception, "PyObject_NEW" );
		return ( NULL );
	}

	// it's the first thing to set.
	self->parent = pycon;

	if ( ( self->dclass = afc_dynamic_class_master_new_instance ( pycon->dynmast, class_name ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "Cannot allocate the Dynamic Class" );
		PyErr_SetString ( pycon->base_exception, "Cannot allocate the Dynamic Class" );
		afc_pyconnector_internal_plugin_dealloc ( self );
		return ( NULL );
	}

	if ( ( self->class_name = afc_string_dup ( class_name ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "Cannot store the class name" );
		PyErr_SetString ( pycon->base_exception, "Cannot store the class name" );
		afc_pyconnector_internal_plugin_dealloc ( self );
		return ( NULL );
	}

	if ( ( self->attribute_requested = afc_string_new ( AFC_PYCONNECTOR_PLUGIN_ATTRIBUTE_NAME_SIZE ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "Cannot allocate the attribute name buffer" );
		PyErr_SetString ( pycon->base_exception, "Cannot allocate the attribute name buffer" );
		afc_pyconnector_internal_plugin_dealloc ( self );
		return ( NULL );
	}

	return ( self );
}


/*******************************
    PyConnectorObject methods
 *******************************/

/*
       NAME: afc_pyconnector_internal_object_dealloc

   SYNOPSIS: void afc_pyconnector_internal_object_dealloc ( PyConnectorObject * self )

DESCRIPTION: frees the memory associated with a PyConnectorObject object. This function
             is called by the Python intepreter when it needs to delete a PyConnectorObject
             object.

     INPUTS: - self - pointer to a valid PyConnectorObject instance

    RESULTS: None

   SEE ALSO: - afc_pyconnector_internal_object_new()
*/
static void afc_pyconnector_internal_object_dealloc ( PyConnectorObject * self )
{
	if ( self->pycon_obj_type ) afc_free ( self->pycon_obj_type );

	PyObject_DEL ( self );
}

/*
       NAME: afc_pyconnector_internal_object_new

   SYNOPSIS: PyObject * afc_pyconnector_internal_object_new ( PyConnector * pycon )

DESCRIPTION: creates a new instance of a PyConnectorObject object.
             A PyConnectorObject object is a wrapper around the PyConnector C structure. When
             the afcpyconnector module is initialized this function is called to create a Python object
             corresponding to the current PyConnector structure. This way the PyConnector structure becomes
             accessible also from a Python script.

             If the allocation of a new PyTypeObject fails, AFC_ERR_NO_MEMORY is
             returned and the PyConnectorError exception is raised.
             If the PyObject_NEW() method used to create a new Python object fails, then AFC_ERR_NO_MEMORY is
             returned and the PyConnectorError exception is raised.

    INPUTS: - pycon - pointer to a valid PyConnector instance.

   RESULTS: - pointer to a PyObject "containing" the pycon instance
            - NULL if something went wrong

  SEE ALSO: - afc_pyconnector_internal_object_dealloc()
*/
static PyObject * afc_pyconnector_internal_object_new ( PyConnector * pycon )
{
	PyTypeObject * newtype = NULL;
	PyConnectorObject * self = NULL;

	// allocate a new PyTypeObject structure

	if ( ( newtype = ( PyTypeObject * ) afc_malloc ( sizeof ( PyTypeObject ) ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "Cannot allocate PyTypeObject" );
		PyErr_SetString ( pycon->base_exception, "Cannot allocate PyTypeObject" );
		return ( NULL );
	}

	// fill in the slots

	/* header */
	//newtype->_ob_next = 0;
	//newtype->_ob_prev = 0;
	newtype->ob_refcnt = 1;
	newtype->ob_type = &PyType_Type;
	newtype->ob_size = 0;
	newtype->tp_name = "pyconnector";
	newtype->tp_basicsize = sizeof ( PyConnectorObject );
	newtype->tp_itemsize = 0;

	/* standard methods */
	newtype->tp_dealloc = ( destructor ) afc_pyconnector_internal_object_dealloc;
	//newtype->tp_getattr = ( getattrfunc ) afc_pyconnector_internal_object_getattr;
	//newtype->tp_setattr = ( setattrfunc ) afc_pyconnector_internal_object_setattr;

	if ( ( self = PyObject_NEW ( PyConnectorObject, newtype ) ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "PyObject_NEW" );
		PyErr_SetString ( pycon->base_exception, "PyObject_NEW" );
		if ( newtype ) afc_free ( newtype );
		return ( NULL );
	}

	// store the data in the new Python object
	self->pycon = pycon;
	self->pycon_obj_type = newtype;

	return ( PyObject * ) self;
}


/***************************************
     Python module "afcpyconnector"
 ***************************************/

/*
       NAME: afc_pyconnector_module_execute

   SYNOPSIS: PyObject * afc_pyconnector_module_execute ( PyObject * self, PyObject * args )

DESCRIPTION: this function is called by the Python intepreter when the module's "execute" function
             is called. The Python interpreter passes the parameters in the /args/ tuple. The two
             mandatory arguments that must be contained in the /args/ tuple are:
             - item 0: name of a class loaded during the module's initialization
             - item 1: name of the method to be executed.
             If at least one of these is missing the PyConnectorError exception is raised. This exception is
             raised also if the specified class doesn't exist. Otherwise the afc_pyconnector_execute()
             function is called to execute the method and its return value is converted to a Python
             object and returned.

     INPUTS: - self - unused
             - args - pointer to a Python's tuple containing the parameters used in the call of the
                      module's "execute" function

    RESULTS: - a Python object with the result returned from the method call, or NULL if something
               went wrong.

   SEE ALSO: - afc_pyconnector_execute()
*/
static PyObject * afc_pyconnector_module_execute ( PyObject * self, PyObject * args )
{
	int count = 0;
	char * class_name = NULL;
	char * method_name = NULL;
	PyConnector * pycon = __internal_pyconnector;
	DynamicClass * dyn = NULL;

	count = PyTuple_Size ( args );

	if ( count < 2 ) {
		PyErr_SetString ( pycon->base_exception, "execute: less parameters than expected" );
		return ( NULL );
	}

	class_name = PyString_AsString ( PyTuple_GetItem ( args, 0 ) );
	method_name = PyString_AsString ( PyTuple_GetItem ( args, 1 ) );

	/* There's no need to raise an exception because it has already been raised by
	   the PyString_AsString function that failed */
	if ( ( class_name == NULL ) || ( method_name == NULL ) ) return ( NULL );

	if ( ( dyn = afc_dictionary_get ( pycon->plugins, class_name ) ) != NULL ) {
		/* If something went wrong an exception has already been set by afc_pyconnector_execute, so here we
		   only have to return NULL to the interpreter */
		if ( afc_pyconnector_execute ( pycon, dyn, method_name, args, 2 ) != AFC_ERR_NO_ERROR ) return ( NULL );

		return afc_pyconnector_internal_convert_result ( pycon, dyn );
	}
	else {
		PyErr_SetString ( pycon->base_exception, afc_string_make ( pycon->buffer, "Unknown class \"%s\"", class_name ) );
		return ( NULL );
	}
}

/*
       NAME: afc_pyconnector_module_new_plugin

   SYNOPSIS: PyObject * afc_pyconnector_module_new_plugin ( PyObject * self, PyObject * args )

DESCRIPTION: this function is called by the Python intepreter when the module's "new_plugin" function
             is called. The Python interpreter passes the parameters in the /args/ tuple. The only
             parameter that must be contained in the /args/ tuple is:
             - item 0: name of the dynamic class whose instance must be created.
             If this is missing the PyConnectorError exception is raised, otherwise the
             afc_pyconnector_internal_plugin_new() function is called: the result is a new
             instance of the specified dynamic class "embedded" in a Python object. The latter
             is returned as the result of the call of the module's "new_plugin" function.

     INPUTS: - self - not used
             - args - pointer to a Python's tuple containing the parameters used in the call of the
                      module's "new_plugin" function

    RESULTS: a new Python object containing the dynamic class' instance.

   SEE ALSO: - afc_pyconnector_internal_plugin_new()
*/
static PyObject * afc_pyconnector_module_new_plugin ( PyObject * self, PyObject * args )
{
	int count = PyTuple_Size ( args );
	char * class_name = NULL;
	PyConnector * pycon = __internal_pyconnector;

	if ( count < 1 ) {
		PyErr_SetString ( pycon->base_exception, "new_plugin: less parameters than expected" );
		return ( NULL );
	}

	class_name = PyString_AsString ( PyTuple_GetItem ( args, 0 ) );
	if ( class_name == NULL ) return ( NULL );

	return ( PyObject * ) afc_pyconnector_internal_plugin_new ( pycon, class_name );
}

/*
       NAME: afc_pyconnector_module_load_plugin

   SYNOPSIS: PyObject * afc_pyconnector_module_load_plugin ( PyObject * self, PyObject * args )

DESCRIPTION: this function is called by the Python intepreter when the module's "load_plugin" function
             is called. The Python interpreter passes the parameters in the /args/ tuple. The
             parameters that must be contained in the /args/ tuple are:
             - item 0: internal name of the dynamic class that must be loaded: this name will be
                       used throughout the python script to create instances of this class;
             - item 1: name of the file contaning the class' code (usually a *.so file).

             If either is missing the PyConnectorError exception is raised. This exception is also
             raised (with specific description messages) if anything goes wrong in the process of
             loading the dynamic class, opening it and transforming it in the corresponding
             PyTypeObject.

     INPUTS: - self - not used
             - args - pointer to a Python's tuple containing the parameters used in the call of the
                      module's "load_plugin" function

    RESULTS: None
*/
static PyObject * afc_pyconnector_module_load_plugin ( PyObject * self, PyObject * args )
{
	int count = PyTuple_Size ( args );
	char * file_name = NULL;
	char * class_name = NULL;
	PyConnector * pycon = __internal_pyconnector;
	int afc_res = AFC_ERR_NO_ERROR;
	char * err_msg = NULL;
	DynamicClass * dyn = NULL;

	if ( count < 2 ) {
		PyErr_SetString ( pycon->base_exception, "load_plugin: less parameters than expected" );
		return ( NULL );
	}

	class_name = PyString_AsString ( PyTuple_GetItem ( args, 0 ) );
	file_name = PyString_AsString ( PyTuple_GetItem ( args, 1 ) );
	if ( ( class_name == NULL ) || ( strcmp ( class_name , "" ) == 0 ) ) return ( NULL );
	if ( ( file_name == NULL ) || ( strcmp ( file_name , "" ) == 0 ) ) return ( NULL );

	// open the dynamic class file
	if ( ( afc_res = afc_dynamic_class_master_load ( pycon->dynmast, class_name, file_name ) ) == AFC_ERR_NO_ERROR ) {

		// create the base instance
    if ( ( dyn = afc_dynamic_class_master_new_instance ( pycon->dynmast, class_name ) ) != NULL ) {
      afc_dictionary_set ( pycon->plugins, class_name, dyn );

			// build the corresponding PyTypeObject
			if ( ( afc_res = afc_pyconnector_internal_build_one_ctype ( pycon, dyn, class_name ) ) != AFC_ERR_NO_ERROR ) {
				PyErr_SetString ( pycon->base_exception, "Cannot create a new CType" );
				return ( NULL );
			}
    }
    else {
			PyErr_SetString ( pycon->base_exception, "Cannot allocate base plugin instance" );
			return ( NULL );
    }

		Py_INCREF ( Py_None );
		return Py_None;
	}
	else {
		switch ( afc_res ) {
			case AFC_DYNAMIC_CLASS_MASTER_ERR_DUPLICATE_NAME:
				err_msg = "A class with the same name already exists";
				break;
			case AFC_DYNAMIC_CLASS_MASTER_ERR_DLOPEN:
				err_msg = "dlopen: could not load class";
				break;
			case AFC_DYNAMIC_CLASS_MASTER_ERR_DLSYM:
				err_msg = "Could not find symbol";
				break;
			default:
				err_msg = "load_plugin: unknown error";
				break;
		}
		PyErr_SetString ( pycon->base_exception, err_msg );
		return ( NULL );
	}
}


/* Methods table

This table contains the association between the name of a function as seen by the Python
intepreter and the actual C function called.
*/

static PyMethodDef PyConnectorMethods[] = {
	{ "new_plugin", afc_pyconnector_module_new_plugin, METH_VARARGS },
	{ "load_plugin", afc_pyconnector_module_load_plugin, METH_VARARGS },
	{ "execute", afc_pyconnector_module_execute, METH_VARARGS },
	{ NULL, NULL, 0 }
};


/*
       NAME: initafcpyconnector

   SYNOPSIS: void initafcpyconnector ()

DESCRIPTION: this function is called by afc_pyconnector_init() to initialize the "afcpyconnector" module.
             This function "creates" the module in the Python intepreter and adds to it some global identifiers.

     INPUTS: None

    RESULTS: None.

       NOTE: a Python's fatal error is raised if something goes wrong.

*/
void initafcpyconnector ()
{
	PyObject * m = NULL;
	PyObject * pyconobj = NULL;
	AFC * afc;

	/* if __internal_pyconnector is NULL it means that this function has been called by the Python
	   intepreter, not from the afc_pyconnector_init function. In other words: when __internal_pyconnector
	   is not NULL there is an application that embeds Python, otherwise not, so we must allocate the
	   memory for a PyConnector object (and a dynamic class master object).
	   We also mustn't forget to create an afc_base object with afc_new: this is needed by all AFC classes
	   to exploit features like logging messages and debugging. */

	if ( __internal_pyconnector == NULL ) {
		if ( ( afc = afc_new () ) == NULL ) {
			Py_FatalError ( "Cannot allocate AFC base" );
			return;
		}


		if ( ( __internal_pyconnector = afc_pyconnector_new () ) == NULL ) {
			afc_delete ( afc );
			Py_FatalError ( "Cannot allocate PyConnector object" );
			return;
		}

		__internal_pyconnector->afc = afc;

		if ( ( __internal_pyconnector->dynmast = afc_dynamic_class_master_new () ) == NULL ) {
			afc_pyconnector_delete ( __internal_pyconnector );
			afc_delete ( afc );
			Py_FatalError ( "Cannot allocate dynamic class master object" );
			return;
		}

		__internal_pyconnector->delete_mode = AFC_PYCONNECTOR_DELETE_DELETE;
	}

	// initialize module
	m = Py_InitModule ( "afcpyconnector", PyConnectorMethods );

	__internal_pyconnector->pymodule = m;

	// create the module's base exception and add it to the module's dictionary
	__internal_pyconnector->base_exception = PyErr_NewException ( "afcpyconnector.PyConnectorError", NULL, NULL );
	PyDict_SetItemString ( PyModule_GetDict ( m ), "PyConnectorError", __internal_pyconnector->base_exception );
	// NOTE: we don't DECREF the base exception: since the exception could be removed from the module by
	// external code, an owned reference to the class is needed to ensure that it will not be discarded.

	// create wrapper Python object around the PyConnector structure and add it to the module's dictionary
	if ( ( pyconobj = afc_pyconnector_internal_object_new ( __internal_pyconnector ) ) != NULL ) {
		PyDict_SetItemString ( PyModule_GetDict ( m ), "pyconnector", pyconobj );
		Py_DECREF ( pyconobj );
	}
	else {
		Py_FatalError ( "Cannot allocate PyConnectorObject structure" );
		return;
	}

	// register cleanup function to be called by Py_Finalize()
	if ( Py_AtExit ( afc_pyconnector_module_finalize ) != 0 )
		Py_FatalError ( "Cannot register the cleanup function" );

	if ( PyErr_Occurred () )
		Py_FatalError ( "Cannot initialize module afcpyconnector" );
}

/*
       NAME: afc_pyconnector_module_finalize

   SYNOPSIS: void afc_pyconnector_module_finalize ()

DESCRIPTION: this function is called by the Python intepreter during its process of finalization.
             This function disposes of the PyConnector object only if it has been created by the
             initafcpyconnector() function.

     INPUTS: None

    RESULTS: None

   SEE ALSO: - initafcpyconnector()
*/
void afc_pyconnector_module_finalize ()
{
	AFC * afc = __internal_pyconnector->afc;
	DynamicClassMaster * dynmast = __internal_pyconnector->dynmast;

	#ifdef AFC_PYCONNECTOR_DEBUG
		fprintf ( stderr, "*** Entering: afc_pyconnector_module_finalize\n" );
		fprintf ( stderr, "*** __internal_pyconnector->delete_mode = %d\n", __internal_pyconnector->delete_mode );
	#endif

	if ( __internal_pyconnector->delete_mode == AFC_PYCONNECTOR_DELETE_DELETE ) {

		#ifdef AFC_PYCONNECTOR_DEBUG
				fprintf ( stderr, "*** Deleting: __internal_pyconnector\n" );
		#endif

		// first thing to delete
		afc_pyconnector_delete ( __internal_pyconnector );

		// second thing
		if ( dynmast ) afc_dynamic_class_master_delete ( dynmast );

		// very last thing to delete
		afc_delete ( afc );
	}
}


/****************
 -- TEST AREA --
****************/

#ifdef TEST_CLASS
int window_get_template ( DynamicClass * dyn )
{
	char * foo = afc_array_master_first ( dyn->args );

	dyn->result = afc_dynamic_class_get_var ( dyn, "dict" );
	dyn->result_type = AFC_DYNAMIC_CLASS_RESULT_TYPE_POINTER;

	printf ( "You've just executed the get_template method in window class (%x)!\n", ( int ) dyn );
	printf ( "foo: %s\n", foo );

	return ( AFC_ERR_NO_ERROR );
}

int window_get_array ( DynamicClass * dyn )
{
	dyn->result = afc_dynamic_class_get_var ( dyn, "array" );
	dyn->result_type = AFC_DYNAMIC_CLASS_RESULT_TYPE_POINTER;

	return AFC_ERR_NO_ERROR;
}

DynamicClass * window_new_instance ()
{
	Dictionary * dict = afc_dictionary_new ();
	Dictionary * dict2 = afc_dictionary_new ();
	ArrayMaster * am = afc_array_master_new ();
	ArrayMaster * am2 = afc_array_master_new ();
	DynamicClass * dyn = afc_dynamic_class_new ();

	if ( dyn == NULL ) return NULL;

	afc_dynamic_class_add_method ( dyn, "get_template", NULL, window_get_template );
	afc_dynamic_class_add_method ( dyn, "get_array", "D", window_get_array );

	afc_dynamic_class_set_var ( dyn, AFC_DYNAMIC_CLASS_VAR_KIND_NUM, "today", ( void * ) 482002 );
	afc_dynamic_class_set_var ( dyn, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, "dict", ( void * ) dict );
	afc_dynamic_class_set_var ( dyn, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, "array", ( void * ) am2 );

	afc_dictionary_set ( dict, "a", am );
	afc_array_master_add ( am, afc_string_dup ( "primo" ), AFC_ARRAY_MASTER_ADD_TAIL );
	afc_array_master_add ( am, afc_string_dup ( "secondo" ), AFC_ARRAY_MASTER_ADD_TAIL );
	afc_dictionary_set ( dict, "b", afc_string_dup ( "chiave b" ) );

	afc_dictionary_set ( dict2, "a", afc_string_dup ( "q" ) );
	afc_dictionary_set ( dict2, "b", afc_string_dup ( "chiave b" ) );

	afc_array_master_add ( am2, dict2, AFC_ARRAY_MASTER_ADD_TAIL );
	afc_array_master_add ( am2, afc_string_dup ( "secondo elemento" ), AFC_ARRAY_MASTER_ADD_TAIL );

	return ( dyn );
}

int window_del_recursive ( void * value )
{
	void * item = NULL;

	switch ( AFC_CLASS_MAGIC( value ) ) {
		case AFC_ARRAY_MASTER_MAGIC:
			item = afc_array_master_first ( value );
			while ( item != NULL ) {
				window_del_recursive ( item );
				item = afc_array_master_next ( value );
			}
			afc_array_master_delete ( value );
			break;

		case AFC_DICTIONARY_MAGIC:
			item = afc_dictionary_first ( value );
			while ( item != NULL ) {
				window_del_recursive ( item );
				item = afc_dictionary_next ( value );
			}
			afc_dictionary_delete ( value );
			break;

		default:
			afc_string_delete ( value );
	}

	return AFC_ERR_NO_ERROR;
}

int window_del_instance ( DynamicClass * dyn )
{
	Dictionary * dict = afc_dynamic_class_get_var ( dyn, "dict" );
	ArrayMaster * am = afc_dynamic_class_get_var ( dyn, "array" );
	void * item = NULL;

	item = afc_dictionary_first ( dict );
	while ( item != NULL ) {
		window_del_recursive ( item );
		item = afc_dictionary_next ( dict );
	}
	afc_dictionary_delete ( dict );

	item = afc_array_master_first ( am );
	while ( item != NULL ) {
		window_del_recursive ( item );
		item = afc_array_master_next ( am );
	}
	afc_array_master_delete ( am );

	if ( dyn != NULL ) afc_dynamic_class_delete ( dyn );
	return ( AFC_ERR_NO_ERROR );
}


/*** MAIN ***/

int main ( int argc, char ** argv )
{
	AFC * afc = afc_new ();
	PyConnector * pycon = NULL;
	DynamicClassMaster * dm = NULL;
	FILE * fp = NULL;

	afc_track_mallocs ( afc );
	afc_set_tags ( afc, AFC_TAG_SHOW_MALLOCS, FALSE, AFC_TAG_SHOW_FREES, FALSE,
		AFC_TAG_LOG_LEVEL, AFC_LOG_WARNING ,AFC_TAG_END );

	pycon = afc_pyconnector_new ();
	dm = afc_dynamic_class_master_new ();

	if ( pycon == NULL ) {
		fprintf ( stderr, "Cannot allocate PyConnector instance\n" );
		return 1;
	}

	afc_dynamic_class_master_add ( dm, "window", NULL, window_new_instance, window_del_instance, NULL );

	if ( afc_pyconnector_init ( pycon, dm, argv [ 0 ] ) == AFC_ERR_NO_ERROR ) {

		if ( ( fp = fopen ( "./testscript.py", "r" ) ) != NULL )
		{
			if ( PyRun_SimpleFile ( fp, "./testscript.py" ) != 0 ) {
				fprintf ( stderr, "Error in executing the script\n" );
			}

			fclose ( fp );
		}

		Py_Finalize ();
	}
	else fprintf ( stderr, "Error in PyConnector initialization\n" );


	/* FIRST delete the PyConnector instance, THEN delete the DynamicClassMaster instance,
	   not the other way around. */
	afc_pyconnector_delete ( pycon );
	afc_dynamic_class_master_delete ( dm );

	afc_delete ( afc );

	return ( 0 );
}
#endif

